﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace PacManLib
{
    /// <summary>
    /// Holds information about one player.
    /// (A player can be either the Pac-Man or a ghost.)
    /// </summary>
    public class PlayerInfo
    {
        #region Public types

        /// <summary>
        /// Directions that players can move.
        /// </summary>
        public enum Direction
        {
            Up,
            Right,
            Down,
            Left,
            NoChange
        }

        #endregion

        #region Public methods and properties

        /// <summary>
        /// Constructor.
        /// </summary>
        public PlayerInfo()
        {
        }

        /// <summary>
        /// Moves the player in the current direction, if possible 
        /// (ie, if not blocked by a wall).
        /// </summary>
        public void movePlayer(Board board)
        {
            // We check if we can move in the desired direction...
            Point newPosition = new Point();
            if (canMove(m_desiredDirection, board, ref newPosition))
            {
                // We can move in the desired direction...
                m_position = newPosition;
                m_currentDirection = m_desiredDirection;
            }
            else
            {
                // We can't move in the desired direction, so we carry
                // on in the current direction if we can...
                if (canMove(m_currentDirection, board, ref newPosition))
                {
                    m_position = newPosition;
                }
            }
        }

        /// <summary>
        /// Gets or sets the player's position.
        /// </summary>
        public Point Position
        {
            get { return m_position; }
            set { m_position = value; }
        }

        /// <summary>
        /// Gets or sets whether the player can turn around.
        /// (Pac-Man can, and ghosts cannot.)
        /// </summary>
        public bool CanTurnAround
        {
            get { return m_canTurnAround; }
            set { m_canTurnAround = value; }
        }

        /// <summary>
        /// Gets or sets the ID if this player is a ghost.
        /// </summary>
        public int GhostID
        {
            get { return m_ghostID; }
            set { m_ghostID = value; }
        }

        /// <summary>
        /// Gets or sets whether the player is scared.
        /// (Only applies to ghosts.)
        /// </summary>
        public bool IsScared
        {
            get { return m_isScared; }
            set { m_isScared = value; }
        }

        /// <summary>
        /// Gets or sets the desired direction. 
        /// The player will move in this direction at the next 
        /// available opportunity.
        /// </summary>
        public Direction DesiredDirection
        {
            get { return m_desiredDirection; }
            set
            {
                Direction requestedDirection = value;
                if (requestedDirection == Direction.NoChange)
                {
                    requestedDirection = m_desiredDirection;
                }

                // Notes on turning-around.
                // Normally ghosts are not allowed to turn around, ie they
                // are not allowed to start moving in the opposite direction
                // to their current direction. However, when they are scared
                // they can move in whichever direction they like.

                if (m_canTurnAround == true || m_isScared == true)
                {
                    // For a player that can turn around (or is scared), any 
                    // direction is acceptable...
                    m_desiredDirection = requestedDirection;
                }
                else
                {
                    // The player cannot turn around, so we check that 
                    // the direction is not the opposite of the current one...
                    if (oppositeDirection(m_currentDirection) != requestedDirection)
                    {
                        m_desiredDirection = requestedDirection;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the current direction of the player.
        /// </summary><remarks>
        /// Note that there is no setter, as you set the desired-direction
        /// rather than the direction itself.
        /// </remarks>
        public Direction CurrentDirection
        {
            get { return m_currentDirection; }
        }

        /// <summary>
        /// Forces a ghost to move if it has got stuck.
        /// </summary>
        public void unstickGhost(Board board)
        {
            // This is called if a ghost cannot move further in its
            // current direction. One of the directions at 90-degrees
            // should work instead...
            Direction newDirection = ninetyDegrees(m_currentDirection);
            Point newPosition = new Point();
            if (canMove(newDirection, board, ref newPosition))
            {
                m_desiredDirection = newDirection;
                movePlayer(board);
            }
            else
            {
                // The direction was not valid, so we try the oposite one...
                newDirection = oppositeDirection(newDirection);
                if (canMove(newDirection, board, ref newPosition))
                {
                    m_desiredDirection = newDirection;
                    movePlayer(board);
                }
            }
        }

        /// <summary>
        /// Returns a copy of this object.
        /// </summary>
        public PlayerInfo createCopy()
        {
            PlayerInfo copy = new PlayerInfo();
            copy.m_position = m_position;
            copy.m_currentDirection = m_currentDirection;
            copy.m_desiredDirection = m_desiredDirection;
            copy.m_canTurnAround = m_canTurnAround;
            copy.m_ghostID = m_ghostID;
            copy.m_isScared = m_isScared;
            return copy;
        }

        /// <summary>
        /// Returns the opposite direction to the one passed in.
        /// </summary>
        public static Direction oppositeDirection(Direction direction)
        {
            switch (direction)
            {
                case Direction.Up: return Direction.Down;
                case Direction.Down: return Direction.Up;
                case Direction.Left: return Direction.Right;
                case Direction.Right: return Direction.Left;
                case Direction.NoChange: return Direction.NoChange;
                default: throw new Exception("Unexpected direction");
            }
        }

        /// <summary>
        /// Returns a direction at ninety degrees to the one passed in.
        /// </summary>
        public static Direction ninetyDegrees(Direction direction)
        {
            switch (direction)
            {
                case Direction.Up: return Direction.Right;
                case Direction.Down: return Direction.Left;
                case Direction.Left: return Direction.Up;
                case Direction.Right: return Direction.Down;
                case Direction.NoChange: return Direction.NoChange;
                default: throw new Exception("Unexpected direction");
            }
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Checks whether we can move in the direction passed in.
        /// </summary>
        private bool canMove(Direction direction, Board board, ref Point newPosition)
        {
            // We find the new position if we move in the direction passed in...
            newPosition = m_position;
            switch (direction)
            {
                case Direction.Up: newPosition.Y--; break;
                case Direction.Down: newPosition.Y++; break;
                case Direction.Left: newPosition.X--; break;
                case Direction.Right: newPosition.X++; break;
            }

            // We check if the player has gone through the 
            // teleport tunnel...
            if (newPosition.X == 28 && newPosition.Y == 14)
            {
                newPosition.X = 0;
            }
            if (newPosition.X == -1 && newPosition.Y == 14)
            {
                newPosition.X = 27;
            }

            // We return true (can move) if the position is not a wall.
            return board.Items[newPosition.X, newPosition.Y] != Board.Item.Wall;
        }

        #endregion

        #region Private data

        // The x-y position of the player on the board...
        private Point m_position = new Point();

        // The direction the player is currently heading in...
        private Direction m_currentDirection = Direction.Left;

        // The direction that the player will move in at the next
        // available opportunity. 
        // A player can say that he desires to move up while he is 
        // currently moving left. He will continue moving left until
        // he comes to a junction where he can start moving up.
        private Direction m_desiredDirection = Direction.Down;

        // True if you are allowed to turn the player to 
        // move in the opposite direction.
        // (Pac-Man can turn around, but ghosts cannot.)
        private bool m_canTurnAround = true;

        // For ghosts, this is a number between 0 - 3...
        private int m_ghostID = -1;

        // True if this player is a ghost and is scared...
        private bool m_isScared = false;

        #endregion

    }
}
